﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace LabMonitorRemote
{
    public abstract class DewkRead : IDewkRead
    {
        //Statics
        protected static Int32 waitPreClear = 100;
        protected static Int32 waitPostRead = 10;
        protected static Int32 waitRead = 1000;
        protected static Int32 waitPostWrite = 200; // See about dropping to 20ms
        protected static Int32 writeTimeout = 10;
        protected static Int32 readTimeout = 1000;

        //Logger Specific variables
        protected IPEndPoint IPAddress;
        protected TcpClient client;
        protected NetworkStream netStream;
        protected Int32 receiveBufferSize;
        protected Int32 numberOfBytesRead;
        protected byte[] recieveBuffer;
        private Int32 numberOfBytesReadLastRead;
        private Boolean readSafe;
        private Boolean terminateLoops = false;
        private LoggerDownload _CallingLoggerDownload;

        public DewkRead(TcpClient TCPClient, NetworkStream NetStream, Int32 BufferSize = 512)
        {
            if (NetStream == null)
            {
                this.IsReadSafe = false;
                OnConnectionFailed();
                string errorMessage = string.Format("NetworkStream is null");
                Console.WriteLine("\t{0}\n", errorMessage);
                
            }
            if (TCPClient == null)
            {
                this.IsReadSafe = false;
                string errorMessage = string.Format("TCPClient is null");
                Console.WriteLine("\t{0}\n", errorMessage);
            }
            else
            {
                this.IsReadSafe = true;
                receiveBufferSize = BufferSize + 10;
                recieveBuffer = new byte[receiveBufferSize];
                //IPAddress = ipEndPoint;
                //ConnectClient();
                client = TCPClient;    // Accept TcpClient from previous command sent to logger
                netStream = NetStream; // Accept NetworkStream from previous command sent to logger
                client.ReceiveBufferSize = receiveBufferSize;
                this.NumBytesRead = 0;
            }
        }

        #region Properties
        public byte[] ReadBuffer
        {
            get { return recieveBuffer; }
            private set { recieveBuffer = value; }
        }

        public Int32 NumBytesRead
        {
            get { return numberOfBytesRead; }
            protected set { numberOfBytesRead = value; }
        }

        protected Int32 NumBytesReadLastRead
        {
            get { return numberOfBytesReadLastRead; }
            private set { numberOfBytesReadLastRead = value; }
        }

        protected Boolean IsReadSafe
        {
            get { return readSafe; }
            private set
            {
                if (readSafe != value)
                    readSafe = value;
            }
        }

        protected void TerminateAllLoops()
        {
            this.TerminateLoops = true;
            IsReadSafe = false;
        }

        protected Boolean TerminateLoops
        {
            get { return terminateLoops; }
            private set
            {
                if (terminateLoops != value)
                    terminateLoops = value;
            }
        }
        public LoggerDownload CallingLoggerDownload
        {
            get { return _CallingLoggerDownload;}
            set
            {
                _CallingLoggerDownload = value;
                _CallingLoggerDownload.DownloadCanceled += new LoggerDownload.DownloadCanceledEventHandler(RespondToDownloadTerminated);
            }

        }
        #endregion Properties

        private void RespondToDownloadTerminated(object sender, EventArgs e)
        {
            TerminateAllLoops();
        }

        void ConnectClient()
        {
            // Create this LoggerDownload TcpClient object
            client = new TcpClient();
            client.NoDelay = true;
            client.Client.NoDelay = true;
            //client.Client.Blocking = true;
            client.ReceiveBufferSize = receiveBufferSize;
            client.ExclusiveAddressUse = false;

            try
            {
                client.Connect(this.IPAddress);
            }
            catch (Exception e)
            {
                Console.WriteLine("\tError connecting to client:");
                Console.WriteLine(e.ToString());
            }

            // Create this LoggerDownload NetworkStream object            

            try
            {
                netStream = client.GetStream();

                // Set netStream properties
                netStream.WriteTimeout = writeTimeout;
                netStream.ReadTimeout = readTimeout;
            }
            catch (InvalidOperationException e)
            {
                Console.WriteLine("\tThe TCPClient is not connected to a remote host...");
                Console.WriteLine(e.ToString());
            }
        }

        public event ConnectionFailedEventHandler ConnectionFailed;
        public delegate void ConnectionFailedEventHandler(DewkRead dr, EventArgs e);
        protected void OnConnectionFailed()
        {
            TerminateAllLoops();
            if (ConnectionFailed != null)
                ConnectionFailed(this, new EventArgs());
        }

        protected void CloseClient()
        {
            OnReadComplete(); // Fire Event
            if (netStream != null) // If null, an error has occured.
            {
                client.Client.Shutdown(SocketShutdown.Receive);
                netStream.Close();
            }
            client.Close();
        }

        public bool ClientIsConnected()
        {
            if (client.Connected == false)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        public event ReadCompleteEventHandler ReadComplete;
        public delegate void ReadCompleteEventHandler(DewkRead dr, EventArgs e);
        private void OnReadComplete()
        {
            if (ReadComplete != null)
            {
                ReadComplete(this, new EventArgs());
            }
        }

        public async void ReadAsync()
        {
            client.ReceiveBufferSize = receiveBufferSize;
            this.NumBytesRead = await netStream.ReadAsync(recieveBuffer, 0, receiveBufferSize);
            CloseClient();
        }

        public abstract void Read();

        /*
        public void Read()
        {
            int offset = 0;
            int bytesReadThisPass = 0;
            bytesReadThisPass = netStream.Read(recieveBuffer, offset, receiveBufferSize);
            offset += bytesReadThisPass;
            Thread.Sleep(waitPostRead);
            this.NumBytesRead = offset;
            CloseClient();
        }*/

        public static Int32 AverageAdditionalWait = 0;
        public static Int32 NumberOfReads = 0;
        public static Int32 NumberOfSleeps = 0;

        protected void ReadFromDewk()
        {
            int bytesReadThisPass = 0;
            int sleepCount = 0;
            NumberOfReads++;

            try
            {
                while (!this.NetStreamDataAvailable() && sleepCount < 1000)
                {
                    if (TerminateLoops)
                        break;
                    else
                    {
                        sleepCount++;
                        Thread.Sleep(1);
                    }
                }
                NumberOfSleeps += sleepCount;


                // errors here debug by repeating download several times
                bytesReadThisPass = netStream.Read(recieveBuffer, NumBytesRead, receiveBufferSize - NumBytesRead); // size - offset ... attempting to go beyond bounds.
            }
            catch (IOException ioe)
            {
                // Connection likely failed.
                Console.WriteLine("IOException in ReadFromDewk() method");
                OnConnectionFailed();
            }
            catch (NullReferenceException nre)
            {
                // Connection likely failed in DewkCommand
                for (Int32 i = 0; i<100; i++)
                    Console.WriteLine("NullReferenceException in ReadFromDewk() method");
                OnConnectionFailed();
            }
            
            NumBytesReadLastRead = bytesReadThisPass;
            Thread.Sleep(waitPostRead);
            this.NumBytesRead += bytesReadThisPass;
        }

        protected void ReadSmallData() // Designed to read smaller packets of data.
        {
            Int32 sleepCount = 0;

            while (!NetStreamDataAvailable() && sleepCount < 1000)
            {
                if (TerminateLoops)
                {
                    break;
                }
                sleepCount++;
                Thread.Sleep(1);
            }

            if (sleepCount < 1000)
            {
                while (NetStreamDataAvailable())
                {
                    if (TerminateLoops)
                    {
                        break;
                    }
                    Thread.Sleep(sleepCount);
                    ReadFromDewk();
                    Thread.Sleep(sleepCount);
                }
            }
            else
            {
                Console.WriteLine("There was no reply from the logger at: {0}", IPAddress.Address.ToString());
            }
        }


        protected Int32 GetInt32(Byte[] buffer, Int32 offset, Int32 length)
        {
            /* ASCII: 48 49 50 51 52 53 54 55 56 57
             * Char:   1  2  3  4  5  6  7  8  9  0
             * Value transfered from LSD to MSD.
            */
            Int32 i = 0;
            while ((buffer[offset] >= 48) && (buffer[offset] <= 57) && (offset < length))
                i = 10 * i + (buffer[offset++] - 48);
            return i;
        }

        protected Boolean NetStreamDataAvailable()
        {
            Boolean response; ;
            try
            {
                response = netStream.DataAvailable;
            }
            catch (NullReferenceException e)
            {
                string message = string.Format("No connected socket available. NetworkStream reference is null when determining data availibility.");
                response = false;
            }
            return response;
        }

    } // CLASS DewkRead

    public class ReadDataSetSizeDewkRead : DewkRead
    {
        Int32 dataSetSize;

        public Int32 DataSetSize
        {
            get { return dataSetSize; }
            private set { dataSetSize = value; }
        }

        public ReadDataSetSizeDewkRead(TcpClient TCPClient, NetworkStream NetStream)
            : base(TCPClient, NetStream)
        {
            //RecieveDataSetSize();  // removed to require manual call after event subscription.
        }

        public override void Read()
        {
            if (IsReadSafe)
            {
                RecieveDataSetSize();
                CloseClient();
            }
            else
            {
                Console.WriteLine("Error at ReadDataSetSizeDewkRead.Read() method. IsReadSafe = false");
                OnConnectionFailed();
            }
        }

        private void RecieveDataSetSize()
        {
            string message = "The logger is building the requested dataset. This can take up to a minute...";
            Console.WriteLine(message + "\n");
            while (!base.NetStreamDataAvailable())
            {
                if (TerminateLoops)
                {
                    break;
                }
                else
                {
                    Console.Write(".");
                    Thread.Sleep(1000);
                }
            }
            if (!TerminateLoops)
            {
                ReadDataSetSize();
                this.DataSetSize = base.GetInt32(base.ReadBuffer, 0, NumBytesRead);
            }
        }

        private void ReadDataSetSize()
        {
            Int32 readErrorCount = 0;
            ReadFromDewk();
            while (recieveBuffer[NumBytesRead - 1] != 13)
            {
                if (TerminateLoops)
                {
                    break;
                }
                //------------
                if (NumBytesReadLastRead != 0 && readErrorCount < 4)
                {
                    ReadFromDewk();
                }
                else if (readErrorCount >= 4)
                {
                    throw new Exception(@"Error reading network stream in " + this.ToString());
                }
                else
                {
                    readErrorCount++;
                }
            }
        }

    } // CLASS ReadDataSetSizeDewkRead

    public class ReadDataBlockDewkRead : DewkRead
    {
        Int32 dataBlockSize;
        Int32 preDataBytesSize;
        bool dataBlockHeaderComplete;

        public ReadDataBlockDewkRead(TcpClient TCPClient, NetworkStream NetStream, Int32 MaxBlockSize = 256)
            : base(TCPClient, NetStream, MaxBlockSize)
        {
            /* Moved to base class level
            client.ReceiveBufferSize = MaxBlockSize + 10;
            */

            // ReadDataBlockFromDewk(); // removed to require manual call after event subscription.
            
        }

        public override void Read()
        {
            if (IsReadSafe)
            {
                ReadDataBlockFromDewk();
                CloseClient();
            }
            else
            {
                Console.WriteLine("Error at ReadDataBlockDewkRead.Read() method. IsReadSafe = false");
                OnConnectionFailed();
            }
        }

        public void ReadDataBlockFromDewk()
        {
            {

                ReadFromDewk();

                Int32 readAttempts = 0;
                while (!BlockHeaderComplete() && readAttempts < 3)
                {
                    if (TerminateLoops)
                    {
                        break;
                    }

                    ReadFromDewk();
                }

                if (BlockHeaderComplete())
                {
                    DataBlockHeaderComplete = true;
                    while (NumBytesRead < DataBlockSize + PreDataBytesSize)
                    {
                        if (TerminateLoops)
                        {
                            break;
                        }

                        ReadFromDewk();
                    }
                }
                else
                {
                    AddWaitPostWriteTime(1);                            // Increase waitPostRead time 1 milliseconds to reduce future errors.
                    DataBlockHeaderComplete = false;
                }
            }
        }

        public Int32 DataBlockSize
        {
            get
            {
                return dataBlockSize;
            }
            private set
            {
                if (value >= 0)
                    dataBlockSize = value;
            }
        }

        public Int32 PreDataBytesSize
        {
            get { return preDataBytesSize; }
            private set { preDataBytesSize = value; }
        }

        public bool DataBlockHeaderComplete
        {
            get { return dataBlockHeaderComplete; }
            private set { dataBlockHeaderComplete = value; }
        }

        //*************************************************************************
        bool BlockHeaderComplete()
        {
            byte[] testArray = new byte[4];
            int sequenceLength = 0;
            int bufferIndex = 0;
            bool sizeTransfered = false;

            while (sequenceLength < 4)
            {
                if ((recieveBuffer[bufferIndex] >= 48) && (recieveBuffer[bufferIndex] <= 57) && sizeTransfered == false)
                {
                    bufferIndex++;
                }
                else
                {
                    sizeTransfered = true;
                    testArray[sequenceLength] = recieveBuffer[bufferIndex];
                    bufferIndex++;
                    sequenceLength++;
                }
            }
            if (sizeTransfered)
            {
                this.DataBlockSize = (GetInt32(recieveBuffer, 0, bufferIndex - sequenceLength));
                this.PreDataBytesSize = bufferIndex;
            }
            return TestBlockHeader(ref testArray);
        } // BlockHeaderComplete


        // TEST IF DATA BLOCK HEADER WAS RECIEVED PROPERLY 
        bool TestBlockHeader(ref byte[] testArray)
        {
            byte[] completeHeader = { 44, 35, 49, 49 };           // Correct sequence after <bytes> portion ,#11 

            if (ArrayEquals(ref completeHeader, ref testArray))   // If the bytes pattern match the completeHeader requirement
            {
                return true;
            }
            else
            {
                return false;
            }
        } // TestBlockHeader

        // TEST EQUALITY OF BYTE ARRAYS
        bool ArrayEquals(ref byte[] array1, ref byte[] array2)
        {
            if (array1.Length == array2.Length)
            {
                for (int i = 0; i < array1.Length; i++)
                {
                    if (array1[i] != array2[i])
                        return false;
                }
                return true;
            }
            return false;
        } //ArrayEquals

        // INCREASE waitPostRead TIME

        void AddWaitPostReadTime(Int32 additionalTime)
        {
            waitPostRead += additionalTime;
            // Write to screen new waitPostRead time
            Console.WriteLine("                              waitPostRead increased to {0}", waitPostRead);
        }

        void AddWaitPostWriteTime(Int32 additionalTime)
        {
            waitPostWrite += additionalTime;
            // Write to screen new waitPostRead time
            Console.WriteLine("                              waitPostWrite increased to {0}", waitPostWrite);
        }
    } // CLASS ReadDataBlockDewkRead

    public class ClearLastSendDewkRead : DewkRead
    {
        public ClearLastSendDewkRead(TcpClient TCPClient, NetworkStream NetStream, Int32 MaxBlockSize = 256)
            : base(TCPClient, NetStream, MaxBlockSize)
        {
            //ReadFromDewk(); // removed to require manual call after event subscription.
            //CloseClient();  // see above
        }

        public override void Read()
        {
            if (IsReadSafe)
            {
                ReadFromDewk();
                CloseClient();
            }
            else
            {
                Console.WriteLine("Error at ClearLastSendDewkRead.Read() method. IsReadSafe = false");
                OnConnectionFailed();
            }
        }

    } // CLASS ClearLastSendDewkRead

    public class IDNDewkRead : DewkRead
    {
        public IDNDewkRead(TcpClient TCPClient, NetworkStream NetStream)
            : base(TCPClient, NetStream)
        {

        }

        public override void Read()
        {
            if (IsReadSafe)
            {
                //ReadIDN();
                ReadSmallData();
                CloseClient();
            }
            else
            {
                Console.WriteLine("Error at IDNDewkRead.Read() method. IsReadSafe = false");
                OnConnectionFailed();
            }
        }
        
        // Use ReadSmalldata() from base class
        /*
        private void ReadIDN()
        {
            Int32 sleepCount = 0;

            // EXCEPTION HERE -- NullReferenceException occurred ...
            while (!NetStreamDataAvailable() && sleepCount < 1000)
            {
                if (TerminateLoops)
                {
                    break;
                }

                sleepCount++;
                Thread.Sleep(1);
            }
            //Console.WriteLine(sleepCount);

            if (sleepCount < 1000)
            {
                while (NetStreamDataAvailable())
                {
                    if (TerminateLoops)
                    {
                        break;
                    }

                    Thread.Sleep(sleepCount);
                    ReadFromDewk();
                    Thread.Sleep(sleepCount);
                }
            }
            else
            {
                Console.WriteLine("There was no reply from the logger at: {0}", IPAddress.Address.ToString());
            }
        }
         */ 
    } // CLASS IDNDewkRead

    public class GetMeasurmentDewkRead : DewkRead
    {
        public GetMeasurmentDewkRead(TcpClient TCPClient, NetworkStream NetStream)
            : base(TCPClient, NetStream)
        {
        }

        public override void Read()
        {
            if (IsReadSafe)
            {
                //ReadMeasurment();
                ReadSmallData();
                CloseClient();
            }
            else
            {
                Console.WriteLine("Error at GetMeasurmentDewkRead.Read() method. IsReadSafe = false");
                OnConnectionFailed();
            }
        }

        // Use ReadSmalldata() from base class
        /*
        private void ReadMeasurment()
        {
            Int32 sleepCount = 0;

            while (!NetStreamDataAvailable() && sleepCount < 1000)
            {
                if (TerminateLoops)
                {
                    break;
                }
                sleepCount++;
                Thread.Sleep(1);
            }

            if (sleepCount < 1000)
            {
                while (NetStreamDataAvailable())
                {
                    if (TerminateLoops)
                    {
                        break;
                    }
                    Thread.Sleep(sleepCount);
                    ReadFromDewk();
                    Thread.Sleep(sleepCount);
                }
            }
            else
            {
                Console.WriteLine("There was no reply from the logger at: {0}", IPAddress.Address.ToString());
            }
        }
         */
        
    }

    public class ReadSimpleDataDewkRead : DewkRead
    {
        public ReadSimpleDataDewkRead(TcpClient TCPClient, NetworkStream NetStream)
            : base(TCPClient, NetStream)
        {
        }

        public override void Read()
        {
            if (IsReadSafe)
            {
                //ReadMeasurment();
                ReadSmallData();
                CloseClient();
            }
            else
            {
                Console.WriteLine("Error at ReadSimpleDataDewkRead.Read() method. IsReadSafe = false");
                OnConnectionFailed();
            }
        }
    } // ReadSimpleDataDewkRead

} // NAMESPACE LabMonitorRemote
